﻿using Irony.Compiler;

namespace SimpleExpressionEvaluator.Parsers.Irony
{
    public class SimpleExpressionLanguage : Grammar
    {
        public SimpleExpressionLanguage()
        {
            CaseSensitive = false;

            NumberLiteral numberLiteral = TerminalFactory.CreateCSharpNumber("Number");

            var boolean = new ConstantTerminal("Boolean");
            boolean.Add("true", true);
            boolean.Add("false", false);
            boolean.Priority = 10;

            var nil = new ConstantTerminal("Null");
            nil.Add("null", null);
            nil.Add("nothing", null);
            nil.Priority = 10;

            var identifier = new IdentifierTerminal("Identifier");

            var stringLiteral = new StringLiteral("String", TermOptions.None);
            stringLiteral.AddStartEnd("'", ScanFlags.AllowDoubledQuote);
            stringLiteral.AddStartEnd("\"", ScanFlags.AllowAllEscapes);

            Terminal dot = Symbol(".", "dot");
            Terminal less = Symbol("<");
            Terminal greater = Symbol(">");
            Terminal LCb = Symbol("(");
            Terminal RCb = Symbol(")");
            Terminal RFb = Symbol("}");
            Terminal LFb = Symbol("{");
            Terminal comma = Symbol(",");
            Terminal LSb = Symbol("[");
            Terminal RSb = Symbol("]");

            Terminal and = Symbol("and");
            and.Priority = 10;

            Terminal or = Symbol("or");
            or.Priority = 10;

            var Expr = new NonTerminal("Expr");
            var BinOp = new NonTerminal("BinOp");
            var LUnOp = new NonTerminal("LUnOp");

            var ExprList = new NonTerminal("ExprList");
            var QualifiedName = new NonTerminal("QualifiedName");
            var FunctionExpression = new NonTerminal("FunctionExpression");
            var Condition = new NonTerminal("Condition");
            var Conditional = new NonTerminal("IfThen");
            var ExprLine = new NonTerminal("ExprLine");
            var ArrayExpression = new NonTerminal("ArrayExpression");

            Expr.Rule = Symbol("null")
                        | boolean
                        | nil
                        | stringLiteral
                        | numberLiteral
                        | QualifiedName
                        | FunctionExpression
                        | Expr + BinOp + Expr
                        | LUnOp + Expr
                        | LCb + Expr + RCb
                        | LFb + QualifiedName + RFb
                        | Conditional
                        | ArrayExpression
                        ;
            ExprList.Rule = MakePlusRule(ExprList, comma, Expr);


            BinOp.Rule = Symbol("+") | "-" | "*" | "%" | "^" | "&" | "|" | "/"
                         | "&&" | "||" | "==" | "!=" | greater | less
                         | ">=" | "<=" | "is" | "<>"
                         | "=" //| "+=" | "-="
                         | "." | and | or;


            LUnOp.Rule = Symbol("-")
                              | "!";

            FunctionExpression.Rule = QualifiedName + LCb + ExprList.Q() + RCb;

            QualifiedName.Rule = identifier | QualifiedName + dot + identifier | "#" + identifier;

            //For simplicity sake, we're not going to deal with multidimensional arrays 
            //at the moment
            //ArrayExpression.Rule = QualifiedName + LSb + ExprList + RSb;
            ArrayExpression.Rule = QualifiedName + LSb + Expr + RSb;

            Condition.Rule = LCb + Expr + RCb;

            Conditional.Rule = "if" + Condition + "then" + Expr |
                               "if" + Condition + "then" + Expr + "else" + Expr |
                               "if" + Condition + "then" + Expr + "otherwise" + Expr;


            ExprLine.Rule = Expr + Eof; //Eof is predefined terminal

            Terminal Comment = new CommentTerminal("Comment", "/*", "*/");
            NonGrammarTerminals.Add(Comment);

            Terminal LineComment = new CommentTerminal("LineComment", "//", "\n");
            NonGrammarTerminals.Add(LineComment);

            Root = ExprLine;

            #region 5. Operators precedence
            RegisterOperators(1, "is", "=", "==", "!=", "<>", ">", "<", ">=", "<=");
            RegisterOperators(2, "+", "-");
            RegisterOperators(3, "*", "/", "%");
            RegisterOperators(4, Associativity.Right, "^");
            RegisterOperators(5, "|", "||", "or");
            RegisterOperators(6, "&", "&&", "and");
            RegisterOperators(7, "!");

            #endregion

            #region 6. Punctuation symbols

            RegisterPunctuation("(", ")", "[", "]", "{", "}", ",", ";");

            #endregion
        }
    }
}
